package com.yeziji.utils;

import cn.hutool.core.util.StrUtil;
import com.yeziji.common.CommonSymbol;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;

/**
 * servlet 处理工具类
 *
 * @author hwy
 * @since 2023/11/13 0:29
 **/
@Slf4j
public class ServletUtils {
    /**
     * 获取请求Body
     *
     * @param request 请求
     * @return {@link String} 请求体参数
     */
    public static String getBodyString(ServletRequest request) {
        try {
            return getBodyByInputStream(request.getInputStream());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 根据输入流获取内容
     *
     * @param inputStream 输入流
     * @return {@link String} 输入流对象
     */
    private static String getBodyByInputStream(InputStream inputStream) {
        StringBuilder sb = new StringBuilder();
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            log.error("写入流异常: {}", e.getMessage(), e);
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    log.error("流关闭异常: {}", e.getMessage(), e);
                }
            }
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    log.error("读取关闭异常: {}", e.getMessage(), e);
                }
            }
        }
        return sb.toString();
    }

    /**
     * 获取客户端访问的 ip 地址
     *
     * <p>
     * <a href="https://blog.csdn.net/java_zhangshuai/article/details/94032479">Java后端获取客户端（用户）真实ip，原理</a>
     * </p>
     *
     * @param request 请求对象
     * @return {@link String} ip 地址
     */
    public static String getIpAddress(HttpServletRequest request) {
        String ip = null;
        String unknown = "unknown";

        // X-Forwarded-For：Squid 服务代理
        String ipAddresses = request.getHeader("X-Forwarded-For");
        if (ipAddresses == null || ipAddresses.isEmpty() || unknown.equalsIgnoreCase(ipAddresses)) {
            // Proxy-Client-IP：apache 服务代理
            ipAddresses = request.getHeader("Proxy-Client-IP");
        }

        if (ipAddresses == null || ipAddresses.isEmpty() || unknown.equalsIgnoreCase(ipAddresses)) {
            // WL-Proxy-Client-IP：webLogic 服务代理
            ipAddresses = request.getHeader("WL-Proxy-Client-IP");
        }

        if (ipAddresses == null || ipAddresses.isEmpty() || unknown.equalsIgnoreCase(ipAddresses)) {
            // HTTP_CLIENT_IP：有些代理服务器
            ipAddresses = request.getHeader("HTTP_CLIENT_IP");
        }

        if (ipAddresses == null || ipAddresses.isEmpty() || unknown.equalsIgnoreCase(ipAddresses)) {
            // X-Real-IP：nginx 服务代理
            ipAddresses = request.getHeader("X-Real-IP");
        }

        // 有些网络通过多层代理，那么获取到的ip就会有多个，一般都是通过逗号（,）分割开来，并且第一个 ip 为客户端的真实 IP
        if (ipAddresses != null && !ipAddresses.isEmpty()) {
            ip = ipAddresses.split(CommonSymbol.COMMA)[0];
        }

        // 还是不能获取到，最后再通过 request.getRemoteAddr(); 获取
        if (ip == null || ip.isEmpty() || unknown.equalsIgnoreCase(ipAddresses)) {
            ip = request.getRemoteAddr();
        }

        // 都没找到就看看什么请求头那么牛逼
        if (ip == null) {
            log.warn("获取真实的 ip 地址失败");
            Enumeration<String> headerNames = request.getHeaderNames();
            while (headerNames.hasMoreElements()) {
                // 打印所有头信息
                String headerName = headerNames.nextElement();
                String header = request.getHeader(headerName);
                log.info("打印请求头: {}:{}", headerName, header);
            }
        }
        return ip;
    }

    /**
     * 获取本机 ip
     *
     * @return {@link String}
     */
    public static String getLocalIpAddress() {
        try {
            return InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
            return "127.0.0.1";
        }
    }

    /**
     * 判断 urls 中是否包含请求的 url
     *
     * <p>如果 urls 存在 /a/* 之类的数据，那么当 /a/b 、 /a/c 等……一系列接口都视为 true, 但是 /a/b/c 就是为 false; 如果要放行 /a/b/c 那么就要用 /a/**
     *
     * @param url        url 地址
     * @param requestUrl 请求 url
     * @return {@link Boolean} - 是否存在
     */
    public static boolean urlContains(String url, String requestUrl) {
        // 开头就 * 就略过
        if (url.startsWith(CommonSymbol.STAR_SIGN)) {
            return true;
        }
        int suffixIndex = url.lastIndexOf(CommonSymbol.LEFT_DIAGONAL_BAR) + 1;
        String suffixStr = url.substring(suffixIndex);
        String prefix = url.substring(0, suffixIndex);
        // 单星号就是最远匹配
        if (StrUtil.equals(suffixStr, CommonSymbol.STAR_SIGN)) {
            return prefix.equals(requestUrl.substring(0, requestUrl.lastIndexOf(CommonSymbol.LEFT_DIAGONAL_BAR) + 1));
        }
        // 双星号以最近匹配(只需要匹配开头即可)
        else {
            // 值相同
            if (StrUtil.equals(suffixStr, CommonSymbol.DOUBLE_STAR_SIGN)) {
                // path 是以 / 开头, 所以可以先取代第一个 /
                return requestUrl.startsWith(prefix);
            }
        }
        // 非星号匹配全地址
        return url.equals(requestUrl);
    }
}
